package com.central.scheduler.plugin.task.datasource.mysql;

import com.central.common.enums.DbType;
import com.central.common.model.DatasourceInfo;
import com.central.scheduler.plugin.task.datasource.AbstractDatasourceProcessor;
import com.central.scheduler.plugin.task.datasource.BaseConnectionParam;
import com.central.scheduler.plugin.task.datasource.ConnectionParam;
import com.central.scheduler.spi.utils.JSONUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import static com.central.scheduler.plugin.task.datasource.PasswordUtils.decodePassword;
import static com.central.scheduler.plugin.task.datasource.PasswordUtils.encodePassword;
import static com.central.scheduler.spi.task.TaskConstants.*;

/**
 * @author Tindy
 * @date 2021/12/23
 * @describe
 */
public class MysqlDatasourceProcessor extends AbstractDatasourceProcessor {

    private final Logger logger = LoggerFactory.getLogger(MysqlDatasourceProcessor.class);

    private static final String AUTO_DESERIALIZE = "autoDeserialize";

    private static final String APPEND_PARAMS = "allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false";


    @Override
    public BaseConnectionParam createConnectionParams(DatasourceInfo datasource) {
        String address = String.format("%s%s:%s", JDBC_MYSQL, datasource.getHost(), datasource.getPort());
        String jdbcUrl = String.format("%s/%s", address, datasource.getDbName());

        MysqlConnectionParam mysqlConnectionParam = new MysqlConnectionParam();
        mysqlConnectionParam.setJdbcUrl(jdbcUrl);
        mysqlConnectionParam.setDatabase(datasource.getDbName());
        mysqlConnectionParam.setAddress(address);
        mysqlConnectionParam.setUser(datasource.getUsername());
        mysqlConnectionParam.setPassword(datasource.getPassword());
        mysqlConnectionParam.setOther(transformOther(datasource.getOther()));
        mysqlConnectionParam.setDriver(datasource.getDriver());
        return mysqlConnectionParam;
    }

    @Override
    public String getDatasourceDriver() {
        return COM_MYSQL_JDBC_DRIVER;
    }

    @Override
    public String getJdbcUrl(ConnectionParam connectionParam) {
        MysqlConnectionParam mysqlConnectionParam = (MysqlConnectionParam) connectionParam;
        String jdbcUrl = mysqlConnectionParam.getJdbcUrl();
        if (StringUtils.isNotEmpty(mysqlConnectionParam.getOther())) {
            return String.format("%s?%s&%s", jdbcUrl, mysqlConnectionParam.getOther(), APPEND_PARAMS);
        }
        return String.format("%s?%s", jdbcUrl, APPEND_PARAMS);
    }

    @Override
    public Connection getConnection(ConnectionParam connectionParam) throws ClassNotFoundException, SQLException {
        MysqlConnectionParam mysqlConnectionParam = (MysqlConnectionParam) connectionParam;
        String driver=StringUtils.isNotEmpty(mysqlConnectionParam.getDriver())?mysqlConnectionParam.getDriver():getDatasourceDriver();
        Class.forName(driver);
        String user = mysqlConnectionParam.getUser();
        if (user.contains(AUTO_DESERIALIZE)) {
            logger.warn("sensitive param : {} in username field is filtered", AUTO_DESERIALIZE);
            user = user.replace(AUTO_DESERIALIZE, "");
        }
        String password = decodePassword(mysqlConnectionParam.getPassword());
        if (password.contains(AUTO_DESERIALIZE)) {
            logger.warn("sensitive param : {} in password field is filtered", AUTO_DESERIALIZE);
            password = password.replace(AUTO_DESERIALIZE, "");
        }
        return DriverManager.getConnection(getJdbcUrl(connectionParam), user, password);
    }

    @Override
    public DbType getDbType() {
        return DbType.MYSQL;
    }


    private String transformOther(String other) {
        StringBuilder otherSb = new StringBuilder();
        if (! StringUtils.isBlank(other)) {
            Map map = JSONUtils.parseObject(other, new TypeReference<LinkedHashMap<String, String>>() {
            });
            if (map.size() > 0) {
                Set<String> keys = map.keySet();
                for (String key : keys) {
                    otherSb.append(String.format("%s=%s&", key, map.get(key)));
                }
                otherSb.deleteCharAt(otherSb.length() - 1);
            }
        }
        return otherSb.toString();
    }

}
